<?php

declare (strict_types=1);

use think\admin\extend\CodeExtend;
use think\admin\extend\HttpExtend;
use think\admin\Helper;
use think\admin\helper\TokenHelper;
use think\admin\Library;
use think\admin\service\AdminService;
use think\admin\service\QueueService;
use think\admin\service\RuntimeService;
use think\admin\service\SystemService;
use think\admin\Storage;
use think\db\Query;
use think\helper\Str;
use think\Model;

if (!function_exists('p')) {
    /**
     * 打印输出数据到文件
     * @param mixed $data 输出的数据
     * @param boolean $new 强制替换文件
     * @param ?string $file 保存文件名称
     * @return false|int
     */
    function p($data, bool $new = false, ?string $file = null)
    {
        return SystemService::putDebug($data, $new, $file);
    }
}
if (!function_exists('m')) {
    /**
     * 动态创建模型对象
     * @param string $name 模型名称
     * @param array $data 初始数据
     * @param string $conn 指定连接
     * @return Model
     */
    function m(string $name, array $data = [], string $conn = ''): Model
    {
        return Helper::buildModel($name, $data, $conn);
    }
}

if (!function_exists('auth')) {
    /**
     * 访问权限检查
     * @param ?string $node
     * @return boolean
     * @throws ReflectionException
     */
    function auth(?string $node): bool
    {
        return AdminService::check($node);
    }
}
if (!function_exists('admuri')) {
    /**
     * 生成后台 URL 地址
     * @param string $url 路由地址
     * @param array $vars PATH 变量
     * @param boolean|string $suffix 后缀
     * @param boolean|string $domain 域名
     * @return string
     */
    function admuri(string $url = '', array $vars = [], $suffix = true, $domain = false): string
    {
        return sysuri('admin/index/index') . '#' . url($url, $vars, $suffix, $domain)->build();
    }
}
if (!function_exists('sysvar')) {
    /**
     * 读写单次请求的内存缓存
     * @param null|string $name 数据名称
     * @param null|mixed $value 数据内容
     * @return null|array|mixed 返回内容
     */
    function sysvar(?string $name = null, $value = null)
    {
        static $swap = [];
        if ($name === '' && $value === '') {
            return $swap = [];
        } elseif (is_null($value)) {
            return is_null($name) ? $swap : ($swap[$name] ?? null);
        } else {
            return $swap[$name] = $value;
        }
    }
}
if (!function_exists('sysuri')) {
    /**
     * 生成最短 URL 地址
     * @param string $url 路由地址
     * @param array $vars PATH 变量
     * @param boolean|string $suffix 后缀
     * @param boolean|string $domain 域名
     * @return string
     */
    function sysuri(string $url = '', array $vars = [], $suffix = true, $domain = false): string
    {
        if (preg_match('#^(https?://|\\|/|@)#', $url)) {
            return Library::$sapp->route->buildUrl($url, $vars)->suffix($suffix)->domain($domain)->build();
        }
        if (count($attr = $url === '' ? [] : explode('/', rtrim($url, '/'))) < 3) {
            $map = [Library::$sapp->http->getName(), Library::$sapp->request->controller(), Library::$sapp->request->action(true)];
            while (count($attr) < 3) array_unshift($attr, $map[2 - count($attr)] ?? 'index');
        }
        $attr[1] = Str::snake($attr[1]);
        [$rcf, $tmp] = [Library::$sapp->config->get('route', []), uniqid('think_admin_replace_temp_vars_')];
        $map = [Str::lower($rcf['default_app'] ?? ''), Str::snake($rcf['default_controller'] ?? ''), Str::lower($rcf['default_action'] ?? '')];
        for ($idx = count($attr) - 1; $idx >= 0; $idx--) if ($attr[$idx] == ($map[$idx] ?: 'index')) $attr[$idx] = $tmp; else break;
        $url = Library::$sapp->route->buildUrl(join('/', $attr), $vars)->suffix($suffix)->domain($domain)->build();
        $ext = is_string($suffix) ? $suffix : ($rcf['url_html_suffix'] ?? 'html');
        $new = preg_replace("#/{$tmp}(\.{$ext})?#", '', $old = parse_url($url, PHP_URL_PATH) ?: '', -1, $count);
        $count > 0 && $suffix && $new && $ext !== '' && $new !== Library::$sapp->request->baseUrl() && $new .= ".{$ext}";
        return str_replace($old, $new ?: '/', $url);
    }
}

if (!function_exists('encode')) {
    /**
     * 加密 UTF8 字符串
     * @param string $content
     * @return string
     */
    function encode(string $content): string
    {
        [$chars, $length] = ['', strlen($string = CodeExtend::text2utf8($content))];
        for ($i = 0; $i < $length; $i++) $chars .= str_pad(base_convert(strval(ord($string[$i])), 10, 36), 2, '0', 0);
        return $chars;
    }
}

if (!function_exists('decode')) {
    /**
     * 解密 UTF8 字符串
     * @param string $content
     * @return string
     */
    function decode(string $content): string
    {
        $chars = '';
        foreach (str_split($content, 2) as $char) {
            $chars .= chr(intval(base_convert($char, 36, 10)));
        }
        return CodeExtend::text2utf8($chars);
    }
}

if (!function_exists('str2arr')) {
    /**
     * 字符串转数组
     * @param string $text 待转内容
     * @param string $separ 分隔字符
     * @param ?array $allow 限定规则
     * @return array
     */
    function str2arr(string $text, string $separ = ',', ?array $allow = null): array
    {
        $items = [];
        foreach (explode($separ, trim($text, $separ)) as $item) {
            if ($item !== '' && (!is_array($allow) || in_array($item, $allow))) {
                $items[] = trim($item);
            }
        }
        return $items;
    }
}
if (!function_exists('arr2str')) {
    /**
     * 数组转字符串
     * @param array $data 待转数组
     * @param string $separ 分隔字符
     * @param ?array $allow 限定规则
     * @return string
     */
    function arr2str(array $data, string $separ = ',', ?array $allow = null): string
    {
        foreach ($data as $key => $item) {
            if ($item === '' || (is_array($allow) && !in_array($item, $allow))) {
                unset($data[$key]);
            }
        }
        return $separ . join($separ, $data) . $separ;
    }
}

if (!function_exists('isDebug')) {
    /**
     * 调试模式运行
     * @return boolean
     */
    function isDebug(): bool
    {
        return RuntimeService::isDebug();
    }
}
if (!function_exists('isOnline')) {
    /**
     * 产品模式运行
     * @return boolean
     */
    function isOnline(): bool
    {
        return RuntimeService::isOnline();
    }
}
if (!function_exists('sysdata')) {
    /**
     * JSON 数据读取与存储
     * @param string $name 数据名称
     * @param mixed $value 数据内容
     * @return mixed
     * @throws \think\admin\Exception
     */
    function sysdata(string $name, $value = null)
    {
        if (is_null($value)) {
            return SystemService::getData($name);
        } else {
            return SystemService::setData($name, $value);
        }
    }
}
if (!function_exists('syspath')) {
    /**
     * 获取文件绝对路径
     * @param string $name 文件路径
     * @param ?string $root 程序根路径
     * @return string
     */
    function syspath(string $name = '', ?string $root = null): string
    {
        if (is_null($root)) $root = Library::$sapp->getRootPath();
        $attr = ['/' => DIRECTORY_SEPARATOR, '\\' => DIRECTORY_SEPARATOR];
        return rtrim($root, '\\/') . DIRECTORY_SEPARATOR . ltrim(strtr($name, $attr), '\\/');
    }
}
if (!function_exists('sysoplog')) {
    /**
     * 写入系统日志
     * @param string $action 日志行为
     * @param string $content 日志内容
     * @return boolean
     */
    function sysoplog(string $action, string $content): bool
    {
        return SystemService::setOplog($action, $content);
    }
}
if (!function_exists('systoken')) {
    /**
     * 生成 CSRF-TOKEN 参数
     * @return string
     */
    function systoken(): string
    {
        return TokenHelper::token();
    }
}
if (!function_exists('sysqueue')) {
    /**
     * 注册异步处理任务
     * @param string $title 任务名称
     * @param string $command 执行内容
     * @param integer $later 延时执行时间
     * @param array $data 任务附加数据
     * @param integer $rscript 任务类型(0单例,1多例)
     * @param integer $loops 循环等待时间
     * @return string
     * @throws \think\admin\Exception
     */
    function sysqueue(string $title, string $command, int $later = 0, array $data = [], int $rscript = 1, int $loops = 0): string
    {
        return QueueService::register($title, $command, $later, $data, $rscript, $loops)->code;
    }
}

if (!function_exists('enbase64url')) {
    /**
     * Base64安全URL编码
     * @param string $string
     * @return string
     */
    function enbase64url(string $string): string
    {
        return CodeExtend::enSafe64($string);
    }
}
if (!function_exists('debase64url')) {
    /**
     * Base64安全URL解码
     * @param string $string
     * @return string
     */
    function debase64url(string $string): string
    {
        return CodeExtend::deSafe64($string);
    }
}

if (!function_exists('xss_safe')) {
    /**
     * 文本内容XSS过滤
     * @param string $text
     * @return string
     */
    function xss_safe(string $text): string
    {
        // 将所有 onxxx= 中的字母 o 替换为符号 ο，注意它不是字母
        $rules = ['#<script.*?<\/script>#is' => '', '#(\s)on(\w+=\S)#i' => '$1οn$2'];
        return preg_replace(array_keys($rules), array_values($rules), trim($text));
    }
}
if (!function_exists('http_get')) {
    /**
     * 以 get 模拟网络请求
     * @param string $url HTTP请求URL地址
     * @param array|string $query GET请求参数
     * @param array $options CURL参数
     * @return boolean|string
     */
    function http_get(string $url, $query = [], array $options = [])
    {
        return HttpExtend::get($url, $query, $options);
    }
}
if (!function_exists('http_post')) {
    /**
     * 以 post 模拟网络请求
     * @param string $url HTTP请求URL地址
     * @param array|string $data POST请求数据
     * @param array $options CURL参数
     * @return boolean|string
     */
    function http_post(string $url, $data, array $options = [])
    {
        return HttpExtend::post($url, $data, $options);
    }
}
if (!function_exists('data_save')) {
    /**
     * 数据增量保存
     * @param Model|Query|string $dbQuery
     * @param array $data 需要保存或更新的数据
     * @param string $key 条件主键限制
     * @param mixed $where 其它的where条件
     * @return boolean|integer
     * @throws \think\admin\Exception
     */
    function data_save($dbQuery, array $data, string $key = 'id', $where = [])
    {
        return SystemService::save($dbQuery, $data, $key, $where);
    }
}
if (!function_exists('down_file')) {
    /**
     * 下载远程文件到本地
     * @param string $source 远程文件地址
     * @param boolean $force 是否强制重新下载
     * @param integer $expire 强制本地存储时间
     * @return string
     */
    function down_file(string $source, bool $force = false, int $expire = 0): string
    {
        return Storage::down($source, $force, $expire)['url'] ?? $source;
    }
}

if (!function_exists('trace_file')) {
    /**
     * 输出异常数据到文件
     * @param \Exception $exception
     * @return boolean
     */
    function trace_file(Exception $exception): bool
    {
        $path = Library::$sapp->getRuntimePath() . 'trace';
        if (!is_dir($path)) mkdir($path, 0777, true);
        $name = substr($exception->getFile(), strlen(syspath()));
        $file = $path . DIRECTORY_SEPARATOR . date('Ymd_His_') . strtr($name, ['/' => '.', '\\' => '.']);
        $json = json_encode($exception instanceof \think\admin\Exception ? $exception->getData() : [], 64 | 128 | 256);
        $class = get_class($exception);
        return false !== file_put_contents($file,
                "[CODE] {$exception->getCode()}" . PHP_EOL .
                "[INFO] {$exception->getMessage()}" . PHP_EOL .
                ($exception instanceof \think\admin\Exception ? "[DATA] {$json}" . PHP_EOL : '') .
                "[FILE] {$class} in {$name} line {$exception->getLine()}" . PHP_EOL .
                "[TIME] " . date('Y-m-d H:i:s') . PHP_EOL . PHP_EOL .
                '[TRACE]' . PHP_EOL . $exception->getTraceAsString()
            );
    }
}
if (!function_exists('format_bytes')) {
    /**
     * 文件字节单位转换
     * @param string|integer $size
     * @return string
     */
    function format_bytes($size): string
    {
        if (is_numeric($size)) {
            $units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
            for ($i = 0; $size >= 1024 && $i < 4; $i++) $size /= 1024;
            return round($size, 2) . ' ' . $units[$i];
        } else {
            return $size;
        }
    }
}
if (!function_exists('format_datetime')) {
    /**
     * 日期格式标准输出
     * @param int|string $datetime 输入日期
     * @param string $format 输出格式
     * @return string
     */
    function format_datetime($datetime, string $format = 'Y年m月d日 H:i:s'): string
    {
        if (empty($datetime)) {
            return '-';
        } elseif (is_numeric($datetime)) {
            return date(lang($format), intval($datetime));
        } elseif ($timestamp = strtotime($datetime)) {
            return date(lang($format), $timestamp);
        } else {
            return $datetime;
        }
    }
}
if (!function_exists('sysconfig')) {
    /**
     * 获取系统参数
     * @param string $category
     * @param string $configKey
     * @param string $default
     * @return mixed|string
     * @throws \think\admin\Exception
     */
    function sysconfig(string $category = '', string $configKey = '', $configValue = null)
    {
        if (is_null($configValue) && is_string($configKey) && is_string($category)) {
            return SystemService::getConfig($category, $configKey);
        } else {
            return SystemService::setConfig($category, $configKey, $configValue);
        }

    }
}
if (!function_exists('getallheaders')) {
    /**
     * 检查是否存在 getallheaders 函数，如果不存在尝试定义一个
     * @return array
     */
    function getallheaders(): array
    {
        $headers = [];
        foreach ($_SERVER as $name => $value) {
            if (strpos($name, 'HTTP_') === 0) {
                $headerName = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))));
                $headers[$headerName] = $value;
            }
        }
        return $headers;
    }
}
if (!function_exists('searchArrByValue')) {
    /**
     * 根据二维数组某个字段的值查找数组
     * @param array $array
     * @param $index
     * @param $value
     * @return array
     */
    function searchArrByValue(array $array, $index, $value): array
    {
        $newArray = [];
        foreach ($array as $key => $item) {
            if (is_array($item) && array_key_exists($index, $item) && $item[$index] === $value) {
                $newArray[$key] = $item;
            }
        }
        return $newArray;
    }
}
if (!function_exists('searchByValue')) {
    /**
     * 根据二维数组某个字段的值查找另一字段的值
     * @param $array
     * @param $keys
     * @param $value
     * @param $field
     * @return string
     */
    function searchByValue(array $array, $keys, $value, $field): string
    {
        foreach ($array as $v) {
            if (is_array($v) && array_key_exists($keys, $v) && $v[$keys] === $value && array_key_exists($field, $v)) {
                return $v[$field];
            }
        }
        return '';
    }
}
if (!function_exists('searchByArr')) {
    /**
     * 根据二维数组某个字段的值查找符合条件的数组
     * @param $array
     * @param $keys
     * @param $value
     * @param $map
     * @return array
     */
    function searchByArr($array, $keys, $value, $map): array
    {
        $newArray = [];
        $mapKey = array_keys($map);
        if (is_array($array) && count($array) > 0) {
            foreach ($array as $v) {
                if ($v[$keys] === $value && $v[$mapKey[0]] === $map[$mapKey[0]]) {
                    $newArray[] = $v;
                }
            }
        }
        return $newArray;
    }
}
if (!function_exists('desensitize')) {
    /**
     * 信息脱敏函数
     * @param string $string 被替换的字符
     * @param int $start 开始明文长度
     * @param int $end 结束明文长度
     * @param string $re 替换字符
     * @return string
     */
    function desensitize(string $string = '', int $start = 0, int $end = 0, string $re = '*'): string
    {
        if (empty($string) || $end <= 0 || empty($re)) {
            return $string;
        }

        $strLen = mb_strlen($string, 'UTF-8');
        if ($start < 0 || $end > $strLen || $start >= $end) {
            return $string;
        }

        $prefix = mb_substr($string, 0, $start, 'UTF-8');
        $suffix = mb_substr($string, $end, null, 'UTF-8');
        $masked = str_repeat($re, $end - $start);

        return $prefix . $masked . $suffix;
    }
}
if (!function_exists('real_ip')) {
    /**
     * 获得用户的真实IP地址
     *
     * @access  public
     * @return  string
     */
    function real_ip(): string
    {
        $ip = '';

        if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
            // 使用逗号分隔的 IP 地址列表，第一个非内部保留地址即为真实 IP
            $ips = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
            foreach ($ips as $ip) {
                $ip = trim($ip);
                if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
                    return $ip;
                }
            }
        }

        if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
            // 检查客户端 IP
            $ip = $_SERVER['HTTP_CLIENT_IP'];
        } elseif (!empty($_SERVER['REMOTE_ADDR'])) {
            // 检查远程地址
            $ip = $_SERVER['REMOTE_ADDR'];
        }

        // 最终验证 IP 地址
        if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
            return $ip;
        }

        // 如果无法获取有效 IP，则返回默认值
        return '0.0.0.0';
    }
}
if (!function_exists('http_post_data')) {
    /**
     * 蚂蚁区块链 post 模拟网络请求
     * @param $url
     * @param $data_string
     * @return array
     */
    function http_post_data($url, $data_string): array
    {
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $data_string);
        curl_setopt($ch, CURLOPT_HTTPHEADER, array(
                'Content-Type: application/json; charset=utf-8',
                'Content-Length: ' . strlen($data_string))
        );
        ob_start();
        curl_exec($ch);
        $return_content = ob_get_contents();
        ob_end_clean();

        $return_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        return array($return_code, $return_content);
    }

}
if (!function_exists('getUnixTimestamp')) {
    /**
     * 13位时间戳转换
     * @return float
     */
    function getUnixTimestamp(): float
    {
        list($s1, $s2) = explode(' ', microtime());
        return (float)sprintf('%.0f', (floatval($s1) + floatval($s2)) * 1000);
    }
}

if (!function_exists('curls')) {
    /**
     * curl请求
     * @param string $url
     * @param int $timeout
     * @return bool|string
     */
    function curls(string $url, int $timeout = 15)
    {
        // 1. 初始化
        $ch = curl_init();
        // 2. 设置选项，包括URL
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_HEADER, 0);
        // 3. 执行并获取HTML文档内容
        $info = curl_exec($ch);
        // 4. 释放curl句柄
        curl_close($ch);

        return $info;
    }
}
if (!function_exists('TimeToSeconds')) {
    /**
     * 时间转换
     * @param $end_time
     * @return int|string
     * @throws Exception
     */
    function timeToSeconds($end_time): int|string
    {
        date_default_timezone_set('Asia/Shanghai');
        $now = time();
        $expires = $end_time - $now;

        if ($expires > 0) {
            $interval = new DateInterval('PT' . $expires . 'S');
            $format_time = '';

            if ($expires < 60) {
                $format_time = $interval->format('%S秒');
            } elseif ($expires < 3600) {
                $format_time = $interval->format('%I分%S秒');
            } elseif ($expires < 86400) {
                $format_time = $interval->format('%H时%I分%S秒');
            } else {
                $days = floor($expires / 86400);
                $hours = floor(($expires % 86400) / 3600);
                $minutes = floor(($expires % 3600) / 60);
                $seconds = $expires % 60;
                $format_time = $days . '天' . $hours . '时' . $minutes . '分' . $seconds . '秒';
            }

            return ltrim($format_time, '0');
        } else {
            return 0;
        }
    }
}
if (!function_exists('get_http_type')) {
    /**
     * 获取当前网址协议
     * @return string
     */
    function get_http_type(): string
    {
        return isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? 'https' : 'http';
    }
}
if (!function_exists('str2time_date')) {
    /**
     * 日期转换时间戳(不保留时间)
     * 例如: 2020-04-01 08:15:08 => 1585670400
     * @param string $date
     * @return false|int
     */
    function str2timeDate(string $date): bool|int
    {
        $timestamp = strtotime($date);
        if ($timestamp === false) {
            return false; // 日期格式不正确，返回 false 或者抛出异常
        }
        return strtotime(date('Y-m-d', $timestamp));
    }

}
if (!function_exists('between_time')) {
    /**
     * 格式化起止时间(为了兼容前端RangePicker组件)
     * 2020-04-01T08:15:08.891Z => 1585670400
     * @param array $times
     * @return array
     */
    function betweenTime(array $times): array
    {
        if (count($times) !== 2) {
            return ['start_time' => null, 'end_time' => null]; // 输入数组不符合要求，返回默认值或者抛出异常
        }

        $start_time = strtotime(trim($times[0], '&quot;'));
        $end_time = strtotime(trim($times[1], '&quot;'));

        return ['start_time' => $start_time, 'end_time' => $end_time];
    }

}
if (!function_exists('returnSquarePoint')) {
    /**
     * 计算正方形区域的四个顶点坐标
     * @param float $longitude 经度
     * @param float $latitude 纬度
     * @param float $distance 距离（单位：千米）
     * @param float $radius 地球半径（单位：千米）
     * @return array 四个顶点的经纬度坐标
     */
    function returnSquarePoint(float $longitude, float $latitude, float $distance = 1, float $radius = 6371): array
    {
        $delta_longitude = 2 * asin(sin($distance / (2 * $radius)) / cos(deg2rad($latitude)));
        $delta_longitude = rad2deg($delta_longitude);

        $delta_latitude = $distance / $radius;
        $delta_latitude = rad2deg($delta_latitude);

        return [
            'left-top' => ['latitude' => $latitude + $delta_latitude, 'longitude' => $longitude - $delta_longitude],
            'right-top' => ['latitude' => $latitude + $delta_latitude, 'longitude' => $longitude + $delta_longitude],
            'left-bottom' => ['latitude' => $latitude - $delta_latitude, 'longitude' => $longitude - $delta_longitude],
            'right-bottom' => ['latitude' => $latitude - $delta_latitude, 'longitude' => $longitude + $delta_longitude]
        ];
    }

}
if (!function_exists('getPointDistance')) {
    /**
     * 计算两个经纬度坐标之间的距离
     * @param float $longitude1 第一个点的经度
     * @param float $latitude1 第一个点的纬度
     * @param float $longitude2 第二个点的经度
     * @param float $latitude2 第二个点的纬度
     * @return string 格式化后的距离字符串
     */
    function getPointDistance(float $longitude1, float $latitude1, float $longitude2, float $latitude2): string
    {
        $earthRadius = 6371; // 地球平均半径，单位：km

        $radLatitude1 = deg2rad($latitude1);
        $radLatitude2 = deg2rad($latitude2);
        $radLongitude1 = deg2rad($longitude1);
        $radLongitude2 = deg2rad($longitude2);

        $deltaLatitude = $radLatitude1 - $radLatitude2;
        $deltaLongitude = $radLongitude1 - $radLongitude2;

        $distance = 2 * asin(sqrt(pow(sin($deltaLatitude / 2), 2) + cos($radLatitude1) * cos($radLatitude2) * pow(sin($deltaLongitude / 2), 2))) * $earthRadius * 1000;

        if ($distance > 1000) {
            return round($distance / 1000, 2) . 'km';
        }
        return (int)$distance . 'm';
    }

}
if (!function_exists('array_iconv')) {
    /**
     * UTF-8编码 GBK编码相互转换/(支持数组) *
     * @param mixed $str 字符串，支持数组传递
     * @param string $in_charset 原字符串编码
     * @param string $out_charset 输出的字符串编码
     * @return array
     */
    function array_iconv($str, string $in_charset = "gbk", string $out_charset = "utf-8"): array
    {
        if (is_array($str)) {
            foreach ($str as $k => $v) {
                $str[$k] = array_iconv($v, $in_charset, $out_charset);
            }
            return $str;
        } else if (is_string($str)) {
            return mb_convert_encoding($str, $out_charset, $in_charset);
        } else {
            return $str;
        }
    }
}
if (!function_exists('base64ImageContent')) {
    /**
     * 将Base64图片转换为本地图片并保存
     * @param $base64_image_content
     * @param $path
     * @return false|string
     */
    /**
     * 将 base64 编码的图片内容保存为文件
     * @param string $base64ImageContent base64 编码的图片内容
     * @param string $path 保存图片的路径
     * @return string|false 成功时返回图片文件路径，失败时返回 false
     */
    function base64ImageContent(string $base64ImageContent, string $path)
    {
        if (preg_match('/^(data:\s*image\/(\w+);base64,)/', $base64ImageContent, $result)) {
            $imageType = $result[2];
            $imagePath = $path . date('Ymd', time()) . "/";
            if (!file_exists($imagePath)) {
                mkdir($imagePath, 0700, true);
            }
            $newImagePath = $imagePath . time() . "_" . CodeExtend::random(10) . ".{$imageType}";
            if (file_put_contents($newImagePath, base64_decode(str_replace($result[1], '', $base64ImageContent)))) {
                return $newImagePath;
            } else {
                // 文件保存失败，可以记录日志或者抛出异常
                return false;
            }
        }
        // 未匹配到合法的 base64 图片内容
        return false;
    }

}

if (!function_exists('base64EncodeImage')) {
    /**
     * 将图片文件转换为 base64 编码的字符串
     * @param string $imageFile 图片文件路径
     * @return string base64 编码的图片字符串
     */
    function base64EncodeImage(string $imageFile): string
    {
        $imageData = file_get_contents($imageFile);
        if ($imageData === false) {
            // 文件读取失败，可以记录日志或者抛出异常
            return '';
        }

        $base64Image = base64_encode($imageData);
        $imageInfo = getimagesize($imageFile);
        if ($imageInfo === false) {
            // 获取图片信息失败，可以记录日志或者抛出异常
            return '';
        }

        return 'data:' . $imageInfo['mime'] . ';base64,' . $base64Image;
    }
}
if (!function_exists('getTimeRange')) {
    /**
     * 获取常见的时间范围
     * @return array 包含上周、上个月、上个季度和去年的日期区间的关联数组
     */
    function getTimeRange(): array
    {
        $timeRange = [];

        $currentTimestamp = time();
        $currentDate = date('Y-m-d', $currentTimestamp);

        $getLastDateRange = function ($interval, $currentDate) {
            $startDate = date('Y-m-d', strtotime($interval, strtotime($currentDate)));
            $endDate = date('Y-m-d', strtotime('-1 day', strtotime($currentDate)));
            return [$startDate, $endDate];
        };

        $timeRange['last_week'] = $getLastDateRange('-1 week', $currentDate);
        $timeRange['last_month'] = $getLastDateRange('-1 month', $currentDate);

        $currentQuarter = ceil(date('n', $currentTimestamp) / 3);
        $lastQuarterStart = date('Y-m-d', strtotime('-' . ($currentQuarter * 3) . ' month', strtotime($currentDate)));
        $lastQuarterEnd = date('Y-m-d', strtotime('-1 day', strtotime($lastQuarterStart . ' +3 month')));
        $timeRange['last_quarter'] = [$lastQuarterStart, $lastQuarterEnd];

        $timeRange['last_year'] = $getLastDateRange('-1 year', $currentDate);

        return $timeRange;
    }
}
if (!function_exists('calculateYoY')) {
    /**
     * 计算同比增长率
     * @param float $currentValue 当前值
     * @param float $previousValue 上一期值
     * @return float 同比增长率，保留两位小数
     */
    function calculateYoY(float $currentValue, float $previousValue): float
    {
        if ($previousValue === 0) {
            // 上一期值为零，无法计算增长率
            return 0.0;
        }

        $growthRate = (($currentValue - $previousValue) / abs($previousValue)) * 100;
        return round($growthRate, 2);
    }

}